module.exports = {


  friendlyName: 'Get compliance information',


  description: '',


  inputs: {
    complianceType: {
      required: true,
      type: 'string',
      isIn: [
        'operatingSystem',
        'firefox',
        'microsoftOffice',
        'flash',
        'chrome',
        'safari',
      ],
    },
    teamApid: {
      description: 'The ID of the Team to filter by, or 0 to only include hosts with no team, or undefined to not filter by any team.',
      type: 'number',
    },
  },


  exits: {

    success: {
      outputFriendlyName: 'Compliance information',
      outputType: {
        patchProgress: 'string',
        versionsInUse: [{}],
        compliantVersionsInUse: [{}],
      }
    },

  },


  fn: async function ({complianceType, teamApid}) {

    let complianceInformation = {
      patchProgress: undefined,
      versionsInUse: [],
      compliantVersionsInUse: []
    };

    let userFriendlyPlatformNamesByCodeName = {
      'darwin': 'macOS',
      'windows': 'Windows',
      'chrome': 'Chrome OS',
      'ubuntu': 'Ubuntu',
      'linux': 'Linux'
    };


    if(complianceType === 'operatingSystem') {
      //  ╔═╗┌─┐┌─┐┬─┐┌─┐┌┬┐┬┌┐┌┌─┐  ┌─┐┬ ┬┌─┐┌┬┐┌─┐┌┬┐┌─┐
      //  ║ ║├─┘├┤ ├┬┘├─┤ │ │││││ ┬  └─┐└┬┘└─┐ │ ├┤ │││└─┐
      //  ╚═╝┴  └─┘┴└─┴ ┴ ┴ ┴┘└┘└─┘  └─┘ ┴ └─┘ ┴ └─┘┴ ┴└─┘
      let allOperatingSystems = await OperatingSystem.find();
      let idsOfCompliantOperatingSystems = _.pluck(_.where(allOperatingSystems, {isCompliant: true}), 'id');
      let operatingSystemsInUse = [];
      let compliantOperatingSystemsInUse = [];
      for(let os of allOperatingSystems){
        let osInfo = {
          name: os.fullName,
          sortByName: os.fullName.toLowerCase(),
          hostCount: os.lastReportedHostCount,
          id: os.id,
          isCompliant: os.isCompliant,
        };
        if(os.isCompliant){
          if(teamApid !== undefined){
            let numberOfHostsOnThisTeamWithThisOs = await Host.count({teamApid, operatingSystem: os.id});
            compliantOperatingSystemsInUse.push({
              name: os.fullName,
              hostCount: numberOfHostsOnThisTeamWithThisOs,
            });
          } else {
            compliantOperatingSystemsInUse.push({
              name: os.fullName,
              hostCount: os.lastReportedHostCount,
            });
          }
        }
        operatingSystemsInUse.push(osInfo);
      }
      complianceInformation.versionsInUse = _.sortByOrder(complianceInformation.versionsInUse, 'sortByName');

      let numberOfHostsToReport = await Host.count({teamApid: teamApid});
      let numberOfHostsOnThisTeamWithACompliantOs = await Host.count({operatingSystem: {in: idsOfCompliantOperatingSystems}, teamApid: teamApid});
      if(idsOfCompliantOperatingSystems.length !== 0){
        complianceInformation.patchProgress = Math.floor(numberOfHostsOnThisTeamWithACompliantOs / numberOfHostsToReport * 100);
      } else {
        complianceInformation.patchProgress = undefined;
      }
      complianceInformation.versionsInUse = _.sortByOrder(operatingSystemsInUse, 'hostCount', 'asc');
      complianceInformation.compliantVersionsInUse = compliantOperatingSystemsInUse;
    } else if(complianceType === 'microsoftOffice') {
      //  ┌┬┐┬┌─┐┬─┐┌─┐┌─┐┌─┐┌─┐┌┬┐  ┌─┐┌─┐┌─┐┬┌─┐┌─┐
      //  │││││  ├┬┘│ │└─┐│ │├┤  │   │ │├┤ ├┤ ││  ├┤
      //  ┴ ┴┴└─┘┴└─└─┘└─┘└─┘└   ┴   └─┘└  └  ┴└─┘└─┘
      let hostsOnThisTeam = [];
      let hostIdsOnThisTeam = [];
      let numberOfInstallsForThisTeam = undefined;
      if(teamApid !== undefined) {
        hostsOnThisTeam = await Host.find({teamApid: teamApid});
        hostIdsOnThisTeam = _.pluck(hostsOnThisTeam, 'id');
        numberOfInstallsForThisTeam = await CriticalInstall.count({softwareType: complianceType, host: {in: hostIdsOnThisTeam}});
      }
      // Because microsoft office is a suite of software, we'll calculate patch progress by number of hosts that have a compliant version installed, instead of the number of individual installs.
      let allMicrosoftOfficeInstalls = await CriticalInstall.find({softwareType: 'microsoftOffice'});
      let uniqueMicrosoftInstallsByHost = _.uniq(allMicrosoftOfficeInstalls, 'host');

      let compliantMicrosoftOfficeInstalls = await CriticalInstall.find({softwareType: 'microsoftOffice', isCompliant: true});
      let uniqueCompliantMicrosoftOfficeInstalls = _.uniq(compliantMicrosoftOfficeInstalls, 'host');
      let microsoftOfficeVersionsInUse = [];
      let compliantMicrosoftOfficeVersionsInUse = [];
      let officeVersionsByPlatform = [];
      let microsoftOfficeVersionsGroupedByPlatform = _.groupBy(allMicrosoftOfficeInstalls, 'platform');
      for(let platform in microsoftOfficeVersionsGroupedByPlatform){
        let uniqueMicrosoftOfficeVersionsForThisPlatform = _.uniq(microsoftOfficeVersionsGroupedByPlatform[platform], 'versionName');
        officeVersionsByPlatform = officeVersionsByPlatform.concat(uniqueMicrosoftOfficeVersionsForThisPlatform);
      }
      for(let microsoftOfficeVersion of officeVersionsByPlatform) {
        let userFriendlyPlatformNameForThisVersion = userFriendlyPlatformNamesByCodeName[microsoftOfficeVersion.platform];
        if(userFriendlyPlatformNameForThisVersion === undefined){
          userFriendlyPlatformNameForThisVersion = microsoftOfficeVersion.platform;
        }
        let hostCountForThisVersion = _.uniq(_.where(allMicrosoftOfficeInstalls, {versionName: microsoftOfficeVersion.versionName}), 'host').length;
        let microsoftOfficeVersionInfo = {
          name: `Microsoft office for ${userFriendlyPlatformNameForThisVersion} `+ microsoftOfficeVersion.versionName,
          id: microsoftOfficeVersion.fleetApid,
          hostCount: hostCountForThisVersion,
          isCompliant: microsoftOfficeVersion.isCompliant
        };
        if(microsoftOfficeVersion.isCompliant){
          if(teamApid !== undefined){
            let allInstallsForThisVersions = _.where(compliantMicrosoftOfficeInstalls, {fleetApid: microsoftOfficeVersion.fleetApid});
            let hostsWithThisVersionInstalled = _.filter(allInstallsForThisVersions, (version)=>{
              return _.contains(hostIdsOnThisTeam, version.host);
            });
            let numberOfHostsWithThisVersionInstalled = hostsWithThisVersionInstalled.length;
            compliantMicrosoftOfficeVersionsInUse.push({
              name: `Microsoft Office for ${userFriendlyPlatformNameForThisVersion} `+ microsoftOfficeVersion.versionName,
              hostCount: numberOfHostsWithThisVersionInstalled,
            });
          } else {

            compliantMicrosoftOfficeVersionsInUse.push({
              name: `Microsoft office for ${userFriendlyPlatformNameForThisVersion} `+ microsoftOfficeVersion.versionName,
              hostCount: hostCountForThisVersion,
            });
          }
        }
        microsoftOfficeVersionsInUse.push(microsoftOfficeVersionInfo);
      }
      if(uniqueCompliantMicrosoftOfficeInstalls.length !== 0) {
        if(teamApid !== undefined){
          if(numberOfInstallsForThisTeam === 0){
            complianceInformation.patchProgress = 'N/A';
          } else {
            let compliantMicrosoftOfficeInstallsOnThisTeam = await CriticalInstall.find({softwareType: 'microsoftOffice', isCompliant: true, host: {in: hostIdsOnThisTeam}});
            let numberOfUniqueHostsOnThisTeamWithCompliantMicrosoftOfficeInstalled = _.uniq(compliantMicrosoftOfficeInstallsOnThisTeam, 'host');
            let uniqueHostsOnThisTeamWithMicrosoftOfficeInstalled = _.filter(uniqueMicrosoftInstallsByHost, (install)=>{
              return _.contains(hostIdsOnThisTeam, install.host);
            });
            if(compliantMicrosoftOfficeInstallsOnThisTeam !== 0) {
              complianceInformation.patchProgress = Math.floor(numberOfUniqueHostsOnThisTeamWithCompliantMicrosoftOfficeInstalled.length / uniqueHostsOnThisTeamWithMicrosoftOfficeInstalled.length * 100);
            } else {
              complianceInformation.patchProgress = 0;
            }
          }
        } else {
          complianceInformation.patchProgress = Math.floor(uniqueCompliantMicrosoftOfficeInstalls.length / uniqueMicrosoftInstallsByHost.length * 100);
        }
      }
      microsoftOfficeVersionsInUse = _.uniq(microsoftOfficeVersionsInUse);
      complianceInformation.versionsInUse = _.sortByOrder(microsoftOfficeVersionsInUse, 'hostCount', 'asc');
      complianceInformation.compliantVersionsInUse = compliantMicrosoftOfficeVersionsInUse;
    } else {
      //  ┌─┐┌┬┐┬ ┬┌─┐┬─┐  ┌─┐┬─┐┬┌┬┐┬┌─┐┌─┐┬    ┌─┐┌─┐┌─┐┌┬┐┬ ┬┌─┐┬─┐┌─┐
      //  │ │ │ ├─┤├┤ ├┬┘  │  ├┬┘│ │ ││  ├─┤│    └─┐│ │├┤  │ │││├─┤├┬┘├┤
      //  └─┘ ┴ ┴ ┴└─┘┴└─  └─┘┴└─┴ ┴ ┴└─┘┴ ┴┴─┘  └─┘└─┘└   ┴ └┴┘┴ ┴┴└─└─┘
      let hostsOnThisTeam = [];
      let hostIdsOnThisTeam = [];
      let numberOfInstallsForThisTeam = undefined;
      if(teamApid !== undefined) {
        hostsOnThisTeam = await Host.find({teamApid: teamApid});
        hostIdsOnThisTeam = _.pluck(hostsOnThisTeam, 'id');
        numberOfInstallsForThisTeam = await CriticalInstall.count({softwareType: complianceType, host: {in: hostIdsOnThisTeam}});
      }
      let allInstallsOfThisType = await CriticalInstall.find({softwareType: complianceType});
      let compliantInstallsOfThisType = await CriticalInstall.find({softwareType: complianceType, isCompliant: true});
      let versionsInUse = [];
      let compliantVersionsInUse = [];
      // Get the unique software items.
      let uniqueVersionsInUse = _.uniq(allInstallsOfThisType, 'fleetApid');
      for(let version of uniqueVersionsInUse) {
        let userFriendlyPlatformNameForThisVersion = userFriendlyPlatformNamesByCodeName[version.platform];
        if(userFriendlyPlatformNameForThisVersion === undefined){
          userFriendlyPlatformNameForThisVersion = version.platform;
        }
        let hostCountForThisVersion = _.where(allInstallsOfThisType, {fleetApid: version.fleetApid}).length;
        // Add information about this version to the versionsInUse array. This is used to populate the <select> options on the compliant versions forms
        versionsInUse.push({
          name: `${_.startCase(complianceType)} for ${userFriendlyPlatformNameForThisVersion} `+ version.versionName,
          id: version.fleetApid,
          hostCount: hostCountForThisVersion,
          isCompliant: version.isCompliant,
        });
        // Add compliant versions to the compliantVersionsInUse array.
        if(version.isCompliant) {
          // If we're filtering by a team, we'll set the host count for each version in the compliantVersionsInUse array to be the number of hosts on the specified team with that version installed.
          if(teamApid !== undefined){
            let allInstallsForThisVersions = _.where(compliantInstallsOfThisType, {fleetApid: version.fleetApid});
            let hostsWithThisVersionInstalled = _.filter(allInstallsForThisVersions, (version)=>{
              return _.contains(hostIdsOnThisTeam, version.host);
            });
            let numberOfHostsWithThisVersionInstalled = hostsWithThisVersionInstalled.length;
            compliantVersionsInUse.push({
              name: `${_.startCase(complianceType)} for ${userFriendlyPlatformNameForThisVersion} `+ version.versionName,
              hostCount: numberOfHostsWithThisVersionInstalled,
            });
          } else {
            // Otherwise, we'll use the host count we used earlier
            compliantVersionsInUse.push({
              name: `${_.startCase(complianceType)} for ${userFriendlyPlatformNameForThisVersion} `+ version.versionName,
              hostCount: hostCountForThisVersion,
            });
          }
        }
      }
      // If a compliant version has been set for this type of software, we'll return patch progress for this complianceType.
      if(compliantInstallsOfThisType.length !== 0) {
        if(teamApid !== undefined){
          // If there are no installs for this complianceType on this team, we will set the patch progress for this complianceType to be 'N/A'.
          // In frontend land, the page will show an empty state for this table
          if(numberOfInstallsForThisTeam === 0){
            complianceInformation.patchProgress = 'N/A';
          } else {
            // If there are installs for this team, we'll use the array of host IDs to get the number of compliant installs.
            let numberOfCompliantInstallsOnThisTeam = await CriticalInstall.count({softwareType: complianceType, isCompliant: true, host: {in: hostIdsOnThisTeam}});
            complianceInformation.patchProgress = Math.floor(numberOfCompliantInstallsOnThisTeam / numberOfInstallsForThisTeam * 100);
          }
        } else {
          complianceInformation.patchProgress = Math.floor(compliantInstallsOfThisType.length / allInstallsOfThisType.length * 100);
        }
      }
      complianceInformation.versionsInUse = _.sortByOrder(versionsInUse, 'hostCount', 'asc');
      complianceInformation.compliantVersionsInUse = compliantVersionsInUse;
    }

    // Send back the result through the success exit.
    return complianceInformation;

  }


};

